﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Ymatou.CommonService;
using ServiceStack.Text;
using System.Diagnostics;
using System.Reflection;

namespace YmtAuth.LogService
{
    public class LogTask
    {
        private static readonly Lazy<LogTask> lazy = new Lazy<LogTask>(() => new LogTask());
        private Timer timerTask;
        private volatile bool isRuning = false;
        private readonly Dictionary<string, BlockingCollection<LogEntityBase>> queue = new Dictionary<string, BlockingCollection<LogEntityBase>>();
        private readonly LogService logService = new LogService();

        public static LogTask Instance { get { return lazy.Value; } }

        private LogTask()
        {
            RegistryLogServiceQueue();
        }
        public void EnQueue(LogEntityBase log)
        {
            if (!CheckIsEnableTimerLog()) return;

            queue[log.LogType].TryAdd(log);
            if (queue[log.LogType].Count > GetLimit)
                TryTakeLog(log.LogType);
        }
        public void Start()
        {
            //SQLLogBatchEable 0 则不写日志
            if (!CheckIsEnableTimerLog())
                return;
            RegisterLogTaskAction();
            StartLogTimerTask();
        }
        public void Stop()
        {
            if (!CheckIsEnableTimerLog()) return;
            try
            {
                foreach (var q in queue) q.Value.CompleteAdding();
                if (isRuning)
                {
                    var list = GetLogEntity(logService.GetInsertBatch);
                    if (list != null && list.Count > 0)
                    {
                        logService.Store(list);
                        ApplicationLog.Debug("all log write to sql");
                    }
                }
                isRuning = false;
                timerTask.Dispose();
                ApplicationLog.Debug("auth log service stop ok...");
            }
            catch (Exception ex)
            {
                ApplicationLog.Error("TimerLogTask stop error", ex);
            }
        }
        public void RunTask()
        {
            try
            {
                var list = GetLogEntity(logService.GetInsertBatch);
                if (list == null || list.Count <= 0) return;
                var stopWatch = Stopwatch.StartNew();
                logService.Store(list);
                stopWatch.Stop();
                if (stopWatch.ElapsedMilliseconds > 500)
                    ApplicationLog.Debug(string.Format("insert {0} to sql,run time {1:N0} ms，memory queue size {2}", list.Count, stopWatch.ElapsedMilliseconds, queue.Count));
            }
            catch (Exception ex)
            {
                ApplicationLog.Error("TimerLogTask RunTask error", ex);
            }
        }
        public void TryTakeLog(string logType)
        {
            var queueSize = queue[logType].Count;
            var getLogSize = queueSize - GetLimit;
            //var stopWatch = Stopwatch.StartNew();
            var list = GetLogEntity(getLogSize,logType);
            if (ConfigurationManager.AppSettings["DumpQueueLogToDist"] == "1")
                logService.DumpQueueLogToDisk(list);
            //stopWatch.Stop();
            //ApplicationLog.Error(string.Format("queue count {0} 超过上限 {1},老数据 {2} 条dump到日志文件,执行耗时 {3:N0} 毫秒", queueSize, GetLimit, getLogSize, stopWatch.ElapsedMilliseconds));
        }
        private void RegisterLogTaskAction()
        {
            logService.RegisterAction("YmatouUserTokenAuthLog", logService._WriteTokenAuthLogToSQL);
            logService.RegisterAction("UserLoginLogHistory", logService._WriteLoginLogToSQL);
            logService.RegisterAction("SafeVerifyLog", logService._WriteSafeVerifyLogToSQL);
            logService.RegisterAction("YmatouThirdPartyUserLoginLog", logService._WriteThirdPartyLogToSQL);

            logService.RegisterAction("YmatouUserRegisterLoginLog", logService._WriteUserRegisterLogToSQL);
            logService.RegisterAction("UserLockedLog", logService._WriteBlackListIpRequestLogToSQL);
            logService.RegisterAction("UpdateUserIpAreaTimeLog", logService._UpdateUserIpAreaAuthTime);
        }
        private void StartLogTimerTask()
        {
            try
            {
                timerTask = new Timer(o =>
                {
                    if (!isRuning)
                    {
                        ApplicationLog.Debug("auth log service not start...");
                        return;
                    }
                    RunTask();
                    timerTask.Change(GetTimerCycle, Timeout.Infinite);
                }, null, Timeout.Infinite, Timeout.Infinite);
                isRuning = true;
                timerTask.Change(GetTimerCycle, Timeout.Infinite);
                ApplicationLog.Debug("TimerLogTask start ok...");
            }
            catch (Exception ex)
            {
                ApplicationLog.Error("TimerLogTask start error", ex);
            }
        }
        private void RegistryLogServiceQueue()
        {
            var assemblyArray = Assembly.GetExecutingAssembly();
            var subLogArray = assemblyArray.GetTypes().Where(t => t.IsSubclassOf(typeof(LogEntityBase)));
            ApplicationLog.Debug(string.Format("注册 {0} 个定时日志类型", subLogArray.Count()));
            foreach (var log in subLogArray)
            {
                var subLog = (LogEntityBase)Activator.CreateInstance(log);
                queue[subLog.LogType] = new BlockingCollection<LogEntityBase>();
            }
        }
        public void EnsureRegistryLogServiceQueue(string logKey)
        {
            Interlocked.MemoryBarrier();
            var exists = !queue.ContainsKey(logKey) ? 0 : 1;
            Func<int> register = () => { queue[logKey] = new BlockingCollection<LogEntityBase>(); return 1; };
            Interlocked.CompareExchange(ref exists, register(), 0);
        }
        private bool CheckIsEnableTimerLog()
        {
            //SQLLogBatchEable 0 则不写日志
            var logBatchSize = ConfigurationManager.AppSettings["SQLLogBatchEable"];
            if (string.IsNullOrEmpty(logBatchSize) || logBatchSize == "0")
            {
                return false;
            }
            return true;
        }
        private List<LogEntityBase> GetLogEntity(int limit)
        {
            LogEntityBase logEntity;
            var list = new List<LogEntityBase>();
            foreach (var q in queue)
            {
                int count = 0;
                while (q.Value.TryTake(out logEntity) && count < limit)
                {
                    list.Add(logEntity);
                    Interlocked.Increment(ref count);
                }
            }
            return list;
        }
        private List<LogEntityBase> GetLogEntity(int limit,string logNameType)
        {
            if(!queue.ContainsKey(logNameType))return  new List<LogEntityBase>();
            LogEntityBase logEntity;
            var list = new List<LogEntityBase>();
            var q = queue[logNameType];
         
            int count = 0;
            while (q.TryTake(out logEntity) && count < limit)
            {
                list.Add(logEntity);
                Interlocked.Increment(ref count);
            }
            
            return list;
        }
        private int GetLimit
        {
            get
            {
                var limit = ConfigurationManager.AppSettings["MemoryQueueLimit"];
                if (string.IsNullOrEmpty(limit)) return 10000;
                return Convert.ToInt32(limit);
            }
        }
        private int GetTimerCycle
        {
            get
            {
                var timerlogcycle = ConfigurationManager.AppSettings["TimerlogCycle"];
                if (string.IsNullOrEmpty(timerlogcycle)) return 3000;
                return Convert.ToInt32(timerlogcycle);
            }
        }
    }
}
